iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0

昨天我們成功建立了完整的路徑查找系統,包括鄰接表建立、起點識別和BFS路徑查找演算法。

今天我們要繼續完成路徑高亮功能的最後一個重要環節:視覺化高亮與使用者體驗優化

讓找到的學習路徑能夠以美觀、直覺的方式呈現在使用者面前,讓學習者一眼就能看出要達到目標需要經過哪些步驟。

視覺化高亮:讓路徑「亮」起來

SVG元素操作

當我們找到路徑後,需要對應的SVG元素進行視覺化處理。在我們的DOT語法中,每條邊都有一個唯一的ID:

// 在生成DOT語法時,為每條邊設定ID
dot += `  "${src}" -> "${tgt}" [id="${src}_${tgt}"];\n`;

然後我們可以通過這個ID找到對應的SVG元素:

/**
 * 高亮單一路徑的函數
 * @param {Array} path - 要高亮的路徑節點陣列,例如 ['A', 'B', 'C']
 */
function highlightSinglePath(path) {
  // 檢查路徑是否有效:至少需要兩個節點才能形成一條邊
  if (path.length < 2) return;
  
  // 取得SVG容器元素,這是我們要操作的可視化圖形
  const svg = zoomInner.querySelector('svg');
  if (!svg) return;
  
  // 遍歷路徑中的每一條邊(相鄰節點之間的連接)
  for (let i = 0; i < path.length - 1; i++) {
    const source = path[i];      // 當前邊的起始節點
    const target = path[i + 1];  // 當前邊的目標節點
    
    // 根據Graphviz的命名規則,邊的ID格式為 "起始節點_目標節點"
    const edgeId = `${source}_${target}`;
    const edgeElement = svg.querySelector(`#${edgeId}`);
    
    // 如果找到了對應的邊元素
    if (edgeElement) {
      // 取得邊的主要視覺元素
      const pathElement = edgeElement.querySelector('path');     // 邊的線條
      const polygonElement = edgeElement.querySelector('polygon'); // 箭頭元素
      
      // 如果存在線條元素,對其應用高亮樣式
      if (pathElement) {
        // 設定邊的顏色為亮綠色,使用 'important' 確保樣式優先級
        pathElement.style.setProperty('stroke', '#00ff00', 'important');
        // 增加邊的粗細,讓高亮效果更明顯
        pathElement.style.setProperty('stroke-width', '10px', 'important');
        // 添加脈衝動畫效果,讓路徑更加醒目
        pathElement.style.setProperty('animation', 'pathPulse 2s infinite', 'important');
        
        // 如果存在箭頭元素,也要對其應用相同的高亮樣式
        if (polygonElement) {
          // 設定箭頭的填充顏色
          polygonElement.style.setProperty('fill', '#00ff00', 'important');
          // 設定箭頭的邊框顏色
          polygonElement.style.setProperty('stroke', '#00ff00', 'important');
        }
      }
    }
  }
}

動畫效果設計

我們添加了脈衝動畫效果,讓高亮的路徑更加醒目:

@keyframes pathPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

.path-highlighted {
  stroke: #00ff00 !important;
  stroke-width: 10px !important;
  animation: pathPulse 2s infinite !important;
}

這個動畫會讓高亮的路徑產生呼吸般的脈衝效果,讓使用者更容易注意到。

多路徑同時高亮

當有多條路徑時,我們可以同時高亮所有路徑:

function highlightAllPaths(allPaths) {
  // 先清除之前的高亮
  clearPathHighlight();
  
  if (allPaths.length === 0) return;
  
  // 高亮每條路徑
  allPaths.forEach((path, index) => {
    highlightSinglePath(path);
  });
}

使用者體驗優化

點擊互動流程

當使用者點擊節點時,完整的互動流程如下:

node.addEventListener('click', (e) => {
  e.stopPropagation();
  const svgNodeId = node.id;
  const nodeLabel = node.querySelector('text')?.textContent || svgNodeId;
  
  // 通過標籤找到實際的節點 ID
  const actualNodeDetail = state.nodeDetails.find(n => n.label === nodeLabel);
  const actualNodeId = actualNodeDetail ? actualNodeDetail.id : svgNodeId;
  
  // 查找並高亮所有路徑
  const allPaths = findPathToNode(actualNodeId);
  if (allPaths.length > 0) {
    highlightPath(allPaths);
  } else {
    clearPathHighlight();
  }
  
  // 顯示節點資訊
  showNodeInfo(actualNodeId, nodeLabel);
});

清除功能

我們提供了一個「清除路徑高亮」按鈕,讓使用者可以隨時清除高亮效果:

function clearPathHighlight() {
  state.highlightedPath.forEach(highlightedItem => {
    if (highlightedItem.edge) {
      highlightedItem.edge.classList.remove('path-highlighted');
      
      if (highlightedItem.path) {
        highlightedItem.path.style.removeProperty('stroke');
        highlightedItem.path.style.removeProperty('stroke-width');
        highlightedItem.path.style.removeProperty('animation');
      }
      
      if (highlightedItem.polygon) {
        highlightedItem.polygon.style.removeProperty('fill');
        highlightedItem.polygon.style.removeProperty('stroke');
      }
    }
  });
  state.highlightedPath = [];
}

使用者介面改進

為了讓使用者體驗更加完整,我們在節點資訊面板中添加了路徑相關的資訊:

function showNodeInfo(nodeId, nodeLabel) {
  // ... 現有的節點資訊顯示邏輯 ...
  
  // 添加路徑資訊
  const allPaths = findPathToNode(nodeId);
  if (allPaths.length > 0) {
    const pathInfo = document.createElement('div');
    pathInfo.className = 'path-info';
    pathInfo.innerHTML = `
      <h4>學習路徑 (${allPaths.length} 條路徑)</h4>
      <div class="path-list">
        ${allPaths.map((path, index) => `
          <div class="path-item">
            <span class="path-number">路徑 ${index + 1}:</span>
            <span class="path-nodes">${path.join(' → ')}</span>
          </div>
        `).join('')}
      </div>
    `;
    nodeInfoContent.appendChild(pathInfo);
  }
}

實際效果展示

完成這些功能後,GASO 平台現在具備了完整的路徑高亮能力:

  1. 自動路徑查找:點擊任何節點都會自動找到所有可能的學習路徑
  2. 視覺化高亮:路徑會以綠色粗線條配合脈衝動畫顯示
  3. 多路徑支援:同一目標的多條路徑會同時高亮顯示
  4. 使用者友善:提供清除功能,操作直覺簡單
  5. 詳細資訊:在節點資訊面板中顯示所有可能的學習路徑

技術亮點回顧

在這兩天的實作中,我們成功整合了多個重要的技術概念:

圖論演算法應用

  • 鄰接表:高效的圖形資料結構
  • BFS演算法:廣度優先搜尋找到所有路徑
  • 循環檢測:避免無限循環的路徑查找

前端視覺化技術

  • SVG操作:動態修改圖形元素樣式
  • CSS動畫:脈衝效果增強視覺吸引力
  • DOM查詢:精確定位SVG元素

使用者體驗設計

  • 直覺互動:點擊節點即可查看路徑
  • 視覺回饋:清楚的高亮效果
  • 資訊完整:詳細的路徑資訊顯示

結語:從地圖到導航系統

透過這兩天的實作,我們成功將靜態的學習地圖轉化為動態的學習導航系統。

這個轉變的意義不僅僅是技術上的進步,更重要的是學習體驗的革新:

  • 從被動觀看到主動導航:學習者不再只是被動地接收資訊,而是可以主動選擇學習路徑
  • 視覺化學習指引:清楚的路徑高亮讓學習變得更有方向感
  • 多路徑探索:同一目標的多條路徑讓學習者可以選擇最適合自己的方式

明天我們將繼續優化這個平台,讓學習體驗變得更加豐富和有趣。

我們明天見!


如果想要看一些我的鐵人賽花邊心得,
也歡迎追蹤我的 ThreadsFacebook


上一篇
Day 6: 讓學習路徑高亮起來 - 路徑查找演算法
下一篇
Day 8: 為學習地圖添加世界地圖背景 - 背景圖整合與視覺優化
系列文
亨利羊帶你 Google Apps Script 從入門到精通:放棄長篇大論的教學吧,你需要的只是一些精心設計的 prompt!24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言